/* 
 * File:   Sprite.cpp
 * Copyright (C) 2008, 2009
 *		J. Daniel Expósito Polo (HellSpawn666)
 *		Sergio Padrino Recio (PadrinatoR)
 *
 * Created on 28 de agosto de 2008, 17:46
 *
 * This file is part of BombProject.
 * 
 * BombProject is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
   
 * BombProject is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with BombProject.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Sprite.h"
#include "ResourceManager.h"
#include "AnimatedSprite.h"

#include <SDL/SDL_rotozoom.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_gfxBlitFunc.h>
#include <math.h>

/**
 * Constructor.
 * @param image Original surface to make the sprite.
 */
Sprite::Sprite(SDL_Surface *image) {
	mHotPointX = mHotPointY = mRealHotPointX = mRealHotPointY = 0;
	mX = mY = mOffsetX = mOffsetY = 0;
	mHeight = 0;
	mScaleX = mScaleY = 1.0;
	mAngle = 0.0;
	mAlpha = 255;
	mVisible = true;
	mHorizontalMirrored = mVerticalMirrored = false;
	
	mOriginalSurface = image;
	mTransformedSurface = NULL;
	
	if(mOriginalSurface != NULL){
		mImageWidth = image->w;
		mImageHeight = image->h;

		mChanged = true;
	}else mChanged = false;
}

/**
 * Destructor
 */
Sprite::~Sprite() {	
	destroy();
}

/**
 * Destroys everything that can be destroyed (within the sprite).
 */
void Sprite::destroy() {
	if(mTransformedSurface != NULL){
		SDL_FreeSurface(mTransformedSurface);
		mTransformedSurface = NULL;
	}
	
	if(mOriginalSurface != NULL){
		ResourceManager::getInstance()->unloadImage(mOriginalSurface);
		mOriginalSurface = NULL;
	}
}

/**
 * Draws the sprite on the specified surface.
 * @param surface Target surface.
 */
int Sprite::draw(SDL_Surface *target_surface) {
	if (mVisible) {
		updateSurface();

		SDL_Rect destPosition;
		
		destPosition.x = this->getRealX() - mRealHotPointX;
		destPosition.y = this->getRealY() - mRealHotPointY;

		return SDL_BlitSurface(mTransformedSurface, 0, target_surface, &destPosition);
	}else return 1;
}

/**
 * Sets horizontal mirroring for the sprite.
 * @param mode Mirror mode. See MirrorMode enum.
 * @see MirrorMode
 */
void Sprite::horizontalMirror(MirrorMode mode) {
	if (mode == MIRROR_ON)
	{
		if (mHorizontalMirrored) return;
		
		mHorizontalMirrored = true;
		notifyChange();
		return;
	}
	
	if (mode == MIRROR_OFF)
	{
		if (!mHorizontalMirrored) return;
		
		mHorizontalMirrored = true;
		notifyChange();
		return;
	}

	//Else... MIRROR_SWITCH
	mHorizontalMirrored = !mHorizontalMirrored;
	notifyChange();
}

/**
 * Sets vertical mirroring for the sprite.
 * @param mode Mirror mode. See MirrorMode enum.
 * @see MirrorMode
 */
void Sprite::verticalMirror(MirrorMode mode) {
	if (mode == MIRROR_ON)
	{
		if (mVerticalMirrored) return;
		
		mVerticalMirrored = true;
		notifyChange();
		return;
	}
	if (mode == MIRROR_OFF)
	{
		if (!mVerticalMirrored) return;
		
		mVerticalMirrored = true;
		notifyChange();
		return;
	}
	
	//Else... MIRROR_SWITCH
	mVerticalMirrored = !mVerticalMirrored;
	notifyChange();
}

/**
 * Sets the absolute sprite's angle.
 * @param degrees The desired angle in degrees.
 */
void Sprite::rotate(double degrees) {
	mAngle = degrees;

	notifyChange();
}

/**
 * Increases the current sprite's angle.
 * @param degrees The desired angle in degrees.
 */
void Sprite::turn(double degrees) {
	mAngle += degrees;
	
	notifyChange();
}

/**
 * Returns X.
 * @return X
 */
Sint16 Sprite::getX() {
	return mX;
}

/**
 * Returns Y.
 * @return Y
 */
Sint16 Sprite::getY() {
	return mY;
}

/**
 * Returns Z.
 * @return Z
 */
Sint16 Sprite::getZ() {
	return mY;
}

/**
 * Returns the sprite's height from the floor, not the bitmap height.
 * @return Height from the floor
 */
Sint16 Sprite::getHeight() {
	return mHeight;
}


/**
 * Returns the offset X.
 * @return Offset X
 */
Sint16 Sprite::getOffsetX() {
	return mOffsetX;
}

/**
 * Returns the offset Y.
 * @return Offset Y
 */
Sint16 Sprite::getOffsetY() {
	return mOffsetY;
}

/**
 * Returns the real X coordinate.
 * @return X real coordinate
 */
Sint16 Sprite::getRealX() {
	return mX + mOffsetX;
}

/**
 * Returns the real Y coordinate (having into account Y coordinate, height over the floor and the offset Y).
 * @return Y real coordinate
 */
Sint16 Sprite::getRealY() {
	return mY - mHeight + mOffsetY;
}

/**
 * Returns the sprite's alpha.
 * @return Alpha
 */
Sint16 Sprite::getAlpha() {
	return mAlpha;
}

/**
 * Sets the reference point for the sprite.
 * @param x X
 * @param y Y
 */
void Sprite::setHotPoint(Sint16 x, Sint16 y) {
	mHotPointX = x;
	mHotPointY = y;
	
	notifyChange();
}

/**
 * Sets the reference point for the sprite from some relative points.
 * @param hotpoint_relative Relative position. Default: HP_HCENTER | HP_VCENTER
 * @see HotPointPosition
 */
void Sprite::setHotPoint(Uint16 hotpoint_relative){
	Sint16 x, y;
	
	if(hotpoint_relative & HP_HCENTER)
		x = mImageWidth / 2;
	else if(hotpoint_relative & HP_LEFT)
		x = 0;
	else if(hotpoint_relative & HP_RIGHT)
		x = mImageWidth;

	if(hotpoint_relative & HP_VCENTER)
		y = mImageHeight / 2;
	else if(hotpoint_relative & HP_TOP)
		y = 0;
	else if(hotpoint_relative & HP_BOTTOM)
		y = mImageHeight;
	
	setHotPoint(x, y);
}

/**
 * Sets the X coordinate.
 * @param x X
 */
void Sprite::setX(Sint16 x) {
	mX = x;
}

/**
 * Sets the Y coordinate.
 * @param y Y
 */
void Sprite::setY(Sint16 y) {
	mY = y;
}

/**
 * Sets the offset X coordinate.
 * @param x Offset X
 */
void Sprite::setOffsetX(Sint16 x) {
	mOffsetX = x;
}

/**
 * Sets the offset Y coordinate.
 * @param y Offset Y
 */
void Sprite::setOffsetY(Sint16 y) {
	mOffsetY = y;
}

/**
 * Sets the height from the floor.
 * @param h Height
 */
void Sprite::setHeight(Sint16 h) {
	mHeight = h;
}

/**
 * Sets the sprite's absolute position.
 * @param x X
 * @param y Y
 * @param h Height
 */
void Sprite::setPosition(Sint16 x, Sint16 y, Sint16 h) {
	mX = x;
	mY = y;
	mHeight = h;
}

/**
 * Increases the sprite's absolute position.
 * @param dx X speed
 * @param dy Y speed
 * @param dh Height speed
 */
void Sprite::move(Sint16 dx, Sint16 dy, Sint16 dh) {
	mX += dx;
	mY += dy;
	mHeight += dh;
}

/**
 * Tells if the sprite is visible or not.
 * @return true if it's visible.
 */
bool Sprite::isVisible() {
	return mVisible;
}

/**
 * Sets sprite's visibility.
 * @param visible Will it be visible?
 */
void Sprite::setVisible(bool visible) {
	mVisible = visible;
}

/**
 * Set sprite's alpha.
 * @param alpha Alpha from 0 (transparent) to 255 (opaque)
 */
void Sprite::setAlpha(Sint16 alpha){
	mAlpha = alpha;
	
	notifyChange();
}

/**
 * Sets the sprite's absolute scale.
 * @param factor_x Scale factor for width
 * @param factor_y Scale factor for height
 */
void Sprite::scale(double factor_x, double factor_y) {
	mScaleX = factor_x;
	mScaleY = factor_y;
	
	notifyChange();
}

/**
 * Marks the sprite as 'changed'.
 */
void Sprite::notifyChange(){
	mChanged = true;
}

/**
 * Updates the sprite if it was changed.
 */
void Sprite::updateSurface(){
	if(!mChanged) return;
	
	if(mTransformedSurface != NULL){
		SDL_FreeSurface(mTransformedSurface);
		mTransformedSurface = NULL;
	}

	if(fmodf(mAngle, 360.0) == 0.0 && mScaleX == 1.0 && mScaleY == 1.0){
		SDL_Surface* newSurface = rotozoomSurfaceXY(mOriginalSurface, mAngle, mScaleX * (mHorizontalMirrored ? -1.0 : 1.0), mScaleY * (mVerticalMirrored ? -1.0 : 1.0), 0);

		//Convert the surface to the same format as the original surface
		mTransformedSurface = SDL_DisplayFormat(newSurface);
		SDL_FreeSurface(newSurface);

		//Get the color key from the original surface
		SDL_SetColorKey(mTransformedSurface, SDL_SRCCOLORKEY, mOriginalSurface->format->colorkey); 

		SDL_SetAlpha(mTransformedSurface, SDL_SRCALPHA, mAlpha);

		//If the sprite has been scaled or rotated, the hot point must be transformed too
		double hpX = mHotPointX - (mImageWidth / 2), hpY= mHotPointY - (mImageHeight / 2);

		double angle = -(M_PI*mAngle)/180.0;
		mRealHotPointX = (hpX * cos(angle) - hpY * sin(angle)) * mScaleX + (mTransformedSurface->w / 2);
		mRealHotPointY = (hpX * sin(angle) + hpY * cos(angle)) * mScaleY + (mTransformedSurface->h / 2);
	}else{
		mTransformedSurface = SDL_CreateRGBSurface(mOriginalSurface->flags, mOriginalSurface->w, mOriginalSurface->h,
				mOriginalSurface->format->BitsPerPixel, mOriginalSurface->format->Rmask,
				mOriginalSurface->format->Gmask, mOriginalSurface->format->Bmask, mOriginalSurface->format->Amask);

		//Sets the colorkey for the new surface
		SDL_SetColorKey(mTransformedSurface, SDL_SRCCOLORKEY, mTransformedSurface->format->colorkey);
		mRealHotPointX = mHotPointX;
		mRealHotPointY = mHotPointY;
	}
	
	mChanged = false;
}
